#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
import sys
import time
import enum
import logging
import threading
import subprocess
import ipaddress
import re

sys.path.append("/home/p4/ipv4_to_ipv6/utils")

from scapy.all import Ether
import p4runtime_sh.shell as sh
import p4runtime_shell_utils as shu
from mininet.util import ipStr

# 日志管理
logger = logging.getLogger(None)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
logger.addHandler(ch)


class PuntReason(enum.Enum):
    UNRECOGNIZED_OPCODE = (1,)
    OPERATION_RESPONSE = (2,)
    ACTIVE_REQUEST = 3


class ControllerOptcode(enum.Enum):
    NO_OP = (0,)
    GENERATE_FIELD_MAP_REQUEST = (1,)
    ACQUIRE_ADDR = 2


# TODO: 改进IPv4地址和IPv6地址映射关系
addr_map = {
    "10.0.1.11": "fe80::200:ff:fe00:1111",
    "10.0.1.22": "fe80::200:ff:fe00:1122",
    "10.0.2.11": "fe80::200:ff:fe00:2211",
    "10.0.2.22": "fe80::200:ff:fe00:2222",
    "10.0.3.11": "fe80::200:ff:fe00:3311",
}


def get_exp_num_packetins(pktin, exp_num_packets, timeout_sec):
    """
    exp_num_packets
    timeout_spec
    @return: pktlist: 在pktin上接收到的报文
    """
    pktlist = []
    pktin.sniff(lambda p: pktlist.append(p), timeout=timeout_sec)
    return pktlist


class Controller:
    CPU_PORT = 510

    def __init__(self, p4info_txt_fname, p4prog_binary_fname):
        """
        完成初始化工作，读取后缀为`.json`以及`.p4info.txtpb`格式的文件
        """
        # 判断文件是否存在

        # 初始化
        logging.info("start controller init.")
        self.p4info_txt_fname = p4info_txt_fname
        self.p4prog_binary_fname = p4prog_binary_fname

        p4info_data = shu.read_p4info_txt_file(p4info_txt_fname=p4info_txt_fname)

        # Create Python dicts from name to integer values, and integer
        # values to names, for the P4_16 serializable enum types
        # PuntReason_t and ControllerOpcode_t once here during setup.
        self.punt_reason_name2int, self.punt_reason_int2name = (
            shu.serializable_enum_dict(p4info_data, "PuntReason_t")
        )
        self.opcode_name2int, self.opcode_int2name = shu.serializable_enum_dict(
            p4info_data, "ControllerOpcode_t"
        )

        self.p4info_obj_map = shu.make_p4info_obj_map(p4info_data)
        self.cpm_packetin_id2data = shu.controller_packet_metadata_dict_key_id(
            self.p4info_obj_map, "packet_in"
        )

    def addController(
        self,
        device_id: int,
        grpc_addr: str,
        verbose: bool = False,
        thrift_port=9090,
        command_txt="./topo/s1-runtime.txt",
        log_dir="build",
    ):

        sh.setup(
            device_id=device_id,
            grpc_addr=grpc_addr,
            election_id=(0, 2),
            config=sh.FwdPipeConfig(self.p4info_txt_fname, self.p4prog_binary_fname),
            verbose=verbose,
        )

        # 建立连接之后读取表 table创建语句和表项添加语句
        from switch_controller import program_switch_cli

        program_switch_cli("s4", thrift_port, command_txt)

        # 监听报文
        packetin = sh.PacketIn()
        while True:
            # 每隔0.01s监听一次，大部分的数据中心网络回路收敛大概100ms左右
            pktlist = get_exp_num_packetins(packetin, 1, timeout_sec=0.01)
            # 解析收到的报文，根据收到的报文采取相应的动作
            if len(pktlist) >= 1:
                pktdata = shu.decode_packet_in_metadata(
                    self.cpm_packetin_id2data, pktlist[0].packet
                )
                # 经过上面处理得到的pktdata为一个字典类型，key构成的集合为 (metadata, payload)
                # 其中metadata也是一个字典，里面又包含属性 (input_port, punt_reason, opcode, operand0, operand1, operand2, operand3，operand4)
                # analyse_pktdata(pktdata['metadata'],sh.PacketOut())
                pkt_metadata = pktdata["metadata"]
                if (
                    pkt_metadata["punt_reason"] == PuntReason.ACTIVE_REQUEST.value
                    and pkt_metadata["opcode"] == ControllerOptcode.ACQUIRE_ADDR.value
                ):
                    # 接下来控制层需要完成如下的操作，生成 PacketOut 报文返回给S4交换机
                    packet_out = sh.PacketOut()
                    # 查询数据库获取IPv4地址对应的IPv6的地址，将它们封装成 PacketOut报文返回给控制器
                    ipv6Addr = map_addr6(ipStr(int(str(pkt_metadata["operand0"]), 10)))
                    packet_out.metadata['input_port'] = str(pkt_metadata["input_port"])
                    packet_out.metadata['opcode'] = str(
                        ControllerOptcode.ACQUIRE_ADDR.value
                    )
                    packet_out.metadata['operand0'] = str(pkt_metadata["operand0"])
                    # 将 ipv6Addr拆分成四个字符串分别赋值给 operand1, operand2, operand3，operand4
                    (
                        packet_out.metadata['operand1'],
                        packet_out.metadata['operand2'],
                        packet_out.metadata['operand3'],
                        packet_out.metadata['operand4'],
                    ) = split_ipv6Addr_to_list(ipv6Addr)
                    packet_out.send()


def map_addr6(ipv4Addr: str):
    """
    TODO: 改进查询方案
    查询数据库获取IPv4地址对应的IPv6的地址
    """
    return addr_map[ipv4Addr]


def hex8_to_dec_str(hex_str: str) -> str:
    """
    将 8 位十六进制字符串转换为十进制字符串
    :param hex_str: 输入的 8 位十六进制字符串（不区分大小写）
    :return: 十进制数字符串
    :raises ValueError: 输入不符合规范时抛出
    """
    # 输入验证
    if not isinstance(hex_str, str):
        raise TypeError("输入必须是字符串类型")

    if len(hex_str) != 8:
        raise ValueError("输入必须为 8 位字符")

    if not re.fullmatch(r"^[0-9A-Fa-f]{8}$", hex_str):
        raise ValueError("包含非法字符（仅允许 0-9 和 A-F）")

    # 转换为十进制整数
    decimal_value = int(hex_str, 16)

    # 转换为字符串（自动去除前导零）
    return str(decimal_value)


def split_ipv6Addr_to_list(ipv6Addr: str):
    """
    将IPv6地址转化为4个32位地址字符串
    """
    try:
        addr = ipaddress.IPv6Address(ipv6Addr)
    except ipaddress.AddressValueError as e:
        raise ValueError(f"Invalid IPv6 address: {e}")

    # 获取完整展开的 IPv6 地址（补全所有省略的 0）
    exploded = addr.exploded
    groups = exploded.split(":")

    if len(groups) != 8:
        raise ValueError("Unexpected format after expanding IPv6 address")

    # 转换为大写并合并为四个部分
    groups_upper = [g.upper() for g in groups]

    groups = [
        groups_upper[0] + groups_upper[1],
        groups_upper[2] + groups_upper[3],
        groups_upper[4] + groups_upper[5],
        groups_upper[6] + groups_upper[7],
    ]

    groups = [hex8_to_dec_str(g) for g in groups]

    return (groups[0], groups[1], groups[2], groups[3])


if __name__ == "__main__":
    p4info_txt_fname = "./build/ipv4_to_ipv6.p4.p4info.txtpb"
    p4prog_binary_fname = "./build/ipv4_to_ipv6.json"

    controller = Controller(p4info_txt_fname, p4prog_binary_fname)

    # 连接S4交换机
    controller.addController(
        device_id=3,
        grpc_addr="localhost:50054",
        verbose=False,
        thrift_port=9093,
        command_txt="./topo/s4-runtime.txt",
        log_dir="build",
    )
